/*
 * Copyright (c) 2023 fred
 * Licensed under the Apache License,Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
import { StringReader } from '../io/StringReader';
import { Element } from '../nodes/Element';
import { HtmlTreeBuilder } from './HtmlTreeBuilder';
import { ParseErrorList } from './ParseErrorList';
import { ParseSettings } from './ParseSettings';
import { TreeBuilder } from './TreeBuilder';
import List from '@ohos.util.List';
import { Node } from '../nodes/Node';
import { Tokeniser } from './Tokeniser';
import { CharacterReader } from './CharacterReader';
import { Document } from '../nodes/Document';
import { Reader } from '../io/Reader';
import { XmlTreeBuilder } from './XmlTreeBuilder';

/**
 Parses HTML or XML into a {@link org.jsoup.nodes.Document}. Generally, it is simpler to use one of the parse methods in
 {@link org.jsoup.Jsoup}.
 <p>Note that a Parser instance object is not threadsafe. To reuse a Parser configuration in a multi-threaded
 environment, use {@link #newInstance()} to make copies. */
export class Parser {
  private _settings: ParseSettings;
  private _errors: ParseErrorList;
  private _treeBuilder: TreeBuilder;
  private _trackPosition = false

  constructor(copy: Parser);
  constructor(treeBuilder: TreeBuilder);
  constructor(ct: any) {
    if (ct instanceof TreeBuilder) {
      this._treeBuilder = ct
      this._settings = ct.defaultSettings()
      this._errors = ParseErrorList.noTracking()
    } else if (ct instanceof Parser) {
      this._treeBuilder = ct._treeBuilder.newInstance()
      this._errors = new ParseErrorList(ct._errors.length)
      this._settings = new ParseSettings(ct._settings.preserveTagCase(), ct._settings.preserveAttributeCase())
      this._trackPosition = ct._trackPosition
    }
  }

  public newInstance(): Parser {
    return new Parser(this)
  }

  public parseInput(html: string | Reader, baseUri: string): Document {
    let inputHtml: Reader
    if (html instanceof Reader) {
      inputHtml = html
    } else {
      inputHtml = new StringReader(html)
    }
    return this._treeBuilder.parse(inputHtml, baseUri, this)
  }

  public parseFragmentInput(fragment: string, context: Element, baseUri: string): List<Node> {
    return this._treeBuilder.parseFragment(fragment, context, baseUri, this);
  }

  /**
   * Get the TreeBuilder currently in use.
   * @return current TreeBuilder.
   */
  public getTreeBuilder(): TreeBuilder {
    return this._treeBuilder
  }

  /**
   * Update the TreeBuilder used when parsing content.
   * @param treeBuilder new TreeBuilder
   * @return this, for chaining
   */
  public setTreeBuilder(treeBuilder: TreeBuilder): Parser {
    this._treeBuilder = treeBuilder
    treeBuilder.parser = this
    return this
  }

  /**
   * Check if parse error tracking is enabled.
   * @return current track error state.
   */
  public isTrackErrors(): boolean {
    return this._errors.getMaxSize() > 0
  }

  /**
   * Enable or disable parse error tracking for the next parse.
   * @param maxErrors the maximum number of errors to track. Set to 0 to disable.
   * @return this, for chaining
   */
  public setTrackErrors(maxErrors: number): Parser {
    this._errors = maxErrors > 0 ? ParseErrorList.tracking(maxErrors) : ParseErrorList.noTracking()
    return this
  }

  /**
   * Retrieve the parse errors, if any, from the last parse.
   * @return list of parse errors, up to the size of the maximum errors tracked.
   * @see #setTrackErrors(int)
   */
  public getErrors(): ParseErrorList {
    return this._errors;
  }

  /**
   Test if position tracking is enabled. If it is, Nodes will have a Position to track where in the original input
   source they were created from. By default, tracking is not enabled.
   * @return current track position setting
   */
  public isTrackPosition(): boolean {
    return this._trackPosition;
  }

  /**
   Enable or disable source position tracking. If enabled, Nodes will have a Position to track where in the original
   input source they were created from.
   @param trackPosition position tracking setting; {@code true} to enable
   @return this Parser, for chaining
   */
  public setTrackPosition(trackPosition: boolean): Parser {
    this._trackPosition = trackPosition
    return this
  }

  /**
   Update the ParseSettings of this Parser, to control the case sensitivity of tags and attributes.
   * @param settings the new settings
   * @return this Parser
   */
  public settings(settings: ParseSettings): Parser {
    this._settings = settings;
    return this;
  }

  /**
   Gets the current ParseSettings for this Parser
   * @return current ParseSettings
   */
  public getSettings(): ParseSettings {
    return this._settings;
  }

  /**
   (An internal method, visible for Element. For HTML parse, signals that script and style text should be treated as
   Data Nodes).
   */
  public isContentForTagData(normalName: string): boolean {
    return this.getTreeBuilder().isContentForTagData(normalName)
  }

  /**
   * Parse HTML into a Document.
   *
   * @param html HTML to parse
   * @param baseUri base URI of document (i.e. original fetch location), for resolving relative URLs.
   *
   * @return parsed Document
   */
  public static parse(html: string, baseUri: string) {
    let treeBuilder = new HtmlTreeBuilder();
    return treeBuilder.parse(new StringReader(html), baseUri, new Parser(treeBuilder));
  }

  /**
   * Parse a fragment of HTML into a list of nodes. The context element, if supplied, supplies parsing context.
   *
   * @param fragmentHtml the fragment of HTML to parse
   * @param context (optional) the element that this HTML fragment is being parsed for (i.e. for inner HTML). This
   * provides stack context (for implicit element creation).
   * @param baseUri base URI of document (i.e. original fetch location), for resolving relative URLs.
   * @param errorList list to add errors to
   *
   * @return list of nodes parsed from the input HTML. Note that the context element, if supplied, is not modified.
   */
  public static parseFragment(fragmentHtml: string, context: Element, baseUri: string,
                              errorList: ParseErrorList | null = null): List<Node> {
    let treeBuilder = new HtmlTreeBuilder()
    let parser = new Parser(treeBuilder)
    if (SyntaxError !== undefined && errorList !== null) {
      parser._errors = errorList
    }
    return treeBuilder.parseFragment(fragmentHtml, context, baseUri, parser)
  }

  /**
   * Parse a fragment of XML into a list of nodes.
   *
   * @param fragmentXml the fragment of XML to parse
   * @param baseUri base URI of document (i.e. original fetch location), for resolving relative URLs.
   * @return list of nodes parsed from the input XML.
   */
  public static parseXmlFragment(fragmentHtml: string, baseUri: string): List<Node> {
    // let treeBuilder = new XmlTreeBuilder()
    // return treeBuilder.parseFragment1(fragmentHtml,baseUri,new Parser(treeBuilder))
    return null
  }

  /**
   * Parse a fragment of HTML into the {@code body} of a Document.
   *
   * @param bodyHtml fragment of HTML
   * @param baseUri base URI of document (i.e. original fetch location), for resolving relative URLs.
   *
   * @return Document, with empty head, and HTML parsed into body
   */
  public static parseBodyFragment(bodyHtml: string, baseUri: string): Document {
    let doc = Document.createShell(baseUri)
    let body = doc.body()
    let nodeList = Parser.parseFragment(bodyHtml, body, baseUri)
    for (let i = nodeList.length - 1; i > 0; i--) {
      nodeList[i].remove()
    }
    for (let node of nodeList) {
      body.appendChild(node)
    }
    return doc
  }

  /**
   * Utility method to unescape HTML entities from a string
   * @param string HTML escaped string
   * @param inAttribute if the string is to be escaped in strict mode (as attributes are)
   * @return an unescaped string
   */
  public static unescapeEntities(str: string, inAttribute: boolean): string {
    let tokeniser = new Tokeniser(new CharacterReader(new StringReader(str)), ParseErrorList.noTracking());
    return tokeniser.unescapeEntities(inAttribute);
  }

  /**
   * Create a new HTML parser. This parser treats input as HTML5, and enforces the creation of a normalised document,
   * based on a knowledge of the semantics of the incoming tags.
   * @return a new HTML parser.
   */
  public static htmlParser(): Parser {
    return new Parser(new HtmlTreeBuilder());
  }

  /**
   * Create a new XML parser. This parser assumes no knowledge of the incoming tags and does not treat it as HTML,
   * rather creates a simple tree directly from the input.
   * @return a new simple XML parser.
   */
  public static  xmlParser():Parser {
    // return new Parser(new XmlTreeBuilder());
    return null
  }

}
